package webreg.model;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Locale;
import java.util.Vector;

import javax.xml.bind.annotation.XmlAccessOrder;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorOrder;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;

import org.apache.log4j.Logger;

import webreg.model.exceptions.EmptyDescriptionException;
import webreg.model.exceptions.EmptyPlaceException;

/** Implementation for a competition
 * 
 * @author STKNOLL
 */
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "competition")
public class Competition implements Cloneable{

	@XmlElement(name = "description")	
	private String mDescription;
	@XmlElement(name = "place")
	private String mPlace;
	@XmlElement(name = "date")
	private Date mDate;
	@XmlElement(name = "maxRegistrationDate")
	private Date mMaxRegistrationDate;	
	private SimpleDateFormat mDateFormatter;
	@XmlElementWrapper(name = "registeredAthlets")	
	@XmlElement(name = "athlet")
	private Vector<Athlet> mAthlets;
	@XmlElement(name = "clubFrom")
	private Club mClubFrom;
	@XmlElement(name = "clubTo")
	private Club mClubTo;
	private static Logger logger = Logger.getLogger( Competition.class );
	
	/** Constructor for a competition
	 */
	public Competition() {
		this.mDescription = "";
		this.mPlace = "";
		this.mDate = new Date();		
		this.mMaxRegistrationDate = new Date();
		this.mDateFormatter = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN);;
		this.mAthlets = new Vector<Athlet>();
		this.mClubFrom = new Club();
		this.mClubTo = new Club();
	}

	/** Constructor for a competition
	 * 
	 * That's the minimal information needed for a competition
	 * 
	 * @param description The title/description if the competition 
	 * @param place The place of the competition
	 * @param date The date of the competition
	 * @throws EmptyPlaceException 
	 */
	public Competition(String description, String place, Date date) throws EmptyDescriptionException, EmptyPlaceException {
		this();
		setDescription(description);
		setPlace(place);
		setDate(date);
	}

	/** Constructor for a competition
	 * 
	 * @param description The description if the competition 
	 * @param place The place of the competition
	 * @param date The date the competition takes place
	 * @param maxRegDate The latest date that a registration is possible
	 * @throws EmptyPlaceException 
	 * @throws EmptyDescriptionException 
	 */
	public Competition(String description, String place, Date date, Date maxRegDate) throws EmptyDescriptionException, EmptyPlaceException {
		this(description, place, date);
		setMaxRegistrationDate(maxRegDate);
	}

	/** Constructor for a competition
	 *
	 * @param description The description if the competition 
	 * @param place The place of the competition
	 * @param date The date the competition takes place
	 * @param maxRegistrationDate The latest date that a registration is possible
	 * @param registeredAthlets The list of registered athlets
	 * @param clubFrom The club from which this registration is
	 * @param clubTo The club for which this registration is
	 *
	 * @return Competition
	 * @throws EmptyPlaceException 
	 * @throws EmptyDescriptionException 
	 */
	public Competition(String description, String place, Date date, Date maxRegistrationDate, Vector<Athlet> registeredAthlets, Club clubFrom, Club clubTo) throws EmptyDescriptionException, EmptyPlaceException {
		this(description, place, date, maxRegistrationDate);
		setAthlets(registeredAthlets);
		setClubFrom(clubFrom);
		setClubTo(clubTo);
	}

	/** Copy constructor
	 *
	 * @param toCopy The competition to copy
	 * @throws EmptyPlaceException 
	 * @throws EmptyDescriptionException 
	 */
	public Competition(Competition toCopy) {
		this();
		mDescription = toCopy.mDescription;
		mPlace = toCopy.mPlace;
		mDateFormatter = toCopy.mDateFormatter;
		mDate = toCopy.mDate;
		mMaxRegistrationDate = toCopy.mMaxRegistrationDate;
		mAthlets = toCopy.mAthlets;
		mClubFrom = toCopy.mClubFrom;
		mClubTo = toCopy.mClubTo;
	}
	
	/** Implementation of the compare function
	 * 
	 * @param toCompare The club used for comparison
	 * @return True if the clubs are equal, else false
	 */
	public boolean equals (Competition toCompare) {
		if (toCompare.mDescription.equals(this.mDescription) == false ) return false;
		if (toCompare.mPlace.equals(this.mPlace) == false ) return false;
		if (toCompare.mDate.equals(this.mDate) == false ) return false;
		if (toCompare.mMaxRegistrationDate.equals(this.mMaxRegistrationDate) == false ) return false;
		if (toCompare.mClubFrom.equals(this.mClubFrom) == false ) return false;
		if (toCompare.mClubTo.equals(this.mClubTo) == false ) return false;
		
		if ( (this.mAthlets != null) && (toCompare.mAthlets != null) ) {
			// test the size first
			if (this.mAthlets.size() != toCompare.mAthlets.size()) return false;
			
			// compare the elements
			Iterator<Athlet> iter = this.mAthlets.iterator();
			Iterator<Athlet> iter2 = toCompare.mAthlets.iterator();
			for (; iter.hasNext() && iter2.hasNext(); ) {
				Athlet ath1 = (Athlet)iter.next();
				Athlet ath2 = (Athlet)iter2.next();
				if (ath1.equals(ath2) == false) return false;
			}			
		}
		
		return true;
	}

	

	/** Return the competition description
	 * @return The description
	 */
	public String getDescription() {
		return mDescription;
	}	
 
	/** Set the competition description
	 * @param description The title/description to set
	 * @throws EmptyDescriptionException 
	 */
	public void setDescription(String description) throws EmptyDescriptionException {
		if ((description == null) || (description.trim().equals(""))) throw new EmptyDescriptionException( "Competition in " + getPlace() );
		this.mDescription = description;
	}

	/** Return the place of the competition
	 * @return The place
	 */
	public String getPlace() {
		return mPlace;
	}

	/** Set the place of the competition
	 * @param place The place to set
	 * @throws EmptyPlaceException 
	 */
	public void setPlace(String place) throws EmptyPlaceException {
		if ((place == null) || (place.trim().equals(""))) throw new EmptyPlaceException( "Competition " + getDescription() );
		this.mPlace = place;
	}

	/** Return the date of the competition
	 *
	 * @return The date
	 */
	public Date getDate() {
		return mDate;
	}		

	/** Set the date of the competition
	 * 
	 * @param date The date to set
	 */
	public void setDate(Date date) {
		this.mDate = date;
	}

	/** Set the date of the competition as string
	 * 
	 * @param date The date to set as string
	 * @throws ParseException 
	 */
	public void setDate(String date) throws ParseException {
		this.mDate = mDateFormatter.parse(date);
	}

	/** Return the date of the competition as string
	 * @return The date as string
	 */
	public String getDateAsString() {
		return mDateFormatter.format(mDate);
	}

	/** Return the max registration date of the competition
	 *
	 * @return The latest registration date
	 */
	public Date getMaxRegistrationDate() {
		return mMaxRegistrationDate;
	}		

	/** Set the max registration date of the competition
	 * 
	 * @param date The max registration date to set
	 */
	public void setMaxRegistrationDate(Date maxRegistrationDate) {
		this.mMaxRegistrationDate = maxRegistrationDate;
	}

	/** Set the max registration date of the competition as string
	 * 
	 * @param date The max registration date to set as string
	 * @throws ParseException 
	 */
	public void setMaxRegistrationDate(String maxRegistrationDate) throws ParseException {
		this.mMaxRegistrationDate = mDateFormatter.parse(maxRegistrationDate);
	}

	/** Return the max registration date of the competition as string
	 * @return The max registration date as string
	 */
	public String getMaxRegistrationDateAsString() {
		return mDateFormatter.format(mMaxRegistrationDate);
	}

	/** Returns the list of registered athlets
	 * @return The list of registered athlets
	 */
	public Vector<Athlet> getAthlets() {
		return mAthlets;
	}	

	/** Sets the list of registered athlets
	 * @param registeredAthlets The list of registered athlets to set
	 */
	public void setAthlets(Vector<Athlet> registeredAthlets) {
		this.mAthlets = registeredAthlets;
	}	

	/** Returns the club from which this registration is
	 * @return The club from which this registration is
	 */
	public Club getClubFrom() {
		return mClubFrom;
	}

	/** Sets the club from which this registration is
	 * @param fromClub The club from which tis registration is 
	 */
	public void setClubFrom(Club fromClub) {
		this.mClubFrom = fromClub;
	}
	
	/** Return the club for which this registration is
	 * @return The club for which this registration 
	 */
	public Club getClubTo() {
		return mClubTo;
	}

	/** Sets the club for which this registration is
	 * @param toClub The club for which this registration is
	 */
	public void setClubTo(Club toClub) {
		this.mClubTo = toClub;
	}

	@Override
	public String toString() {
		String retValue = getDescription() + ", " + getDateAsString() + ", " + getPlace();
		return retValue;
	}

	/** Add an athlet to the competition
	 * 
	 * @param toAdd The athlet to add
	 */
	public void addElement(Athlet toAdd) {
		mAthlets.add(toAdd);		
	}

	/** Add an athet let the competition before another athlet
	 * 
	 * @param beforeAthlet The athlet before adding the new athlet 
	 * @param toAdd The new athlet
	 */
	public int addElement(Athlet beforeAthlet, Athlet toAdd) {
		int allAthletsSize = mAthlets.size();
		int returnValue = -1;
		for (int i = 0; i < allAthletsSize; i++)
		{
			Athlet athFound = mAthlets.get(i);
			if (athFound.equals(beforeAthlet) == true) {
	    		mAthlets.add(i, toAdd);
	    		returnValue = i;
	    		break;
	    	}
	    }
	    return returnValue;
	}
	
	/** Add a disciplin to a the competition
	 * 
	 * @param athletIndex The index of for the athlet where the disciplin should be added
	 * @param toAdd The disciplin to add
	 */
	public void addElement(int athletIndex, Disciplin toAdd) {
		Athlet athletToAddTo = mAthlets.get(athletIndex);
		Vector<Disciplin> existingDiscVector = athletToAddTo.getDisciplins();
		existingDiscVector.add(toAdd);
		athletToAddTo.setDisciplins(existingDiscVector);
		mAthlets.set( athletIndex, athletToAddTo);
	}
	
	/** Add an disciplin to an athlet of the competition
	 * 
	 * @param toAddIn The athlet to add the disciplin to
	 * @param toAdd The disciplin to add
	 */
	public int addElement(Athlet toAddIn, Disciplin toAdd) {
		int allAthletsSize = mAthlets.size();
		int returnValue = -1;
		for (int i = 0; i < allAthletsSize; i++)
		{
			Athlet athFound = mAthlets.get(i);
			if (athFound.equals(toAddIn) == true) {
				// add to this athlet
				addElement(i, toAdd);
				returnValue = i;		// save the index
				break;
			}			
		}
		return returnValue;
	}

	/** Add a disciplin to an athlet before another of its disciplins 
	 * 
	 * @param toAddIn The athlet to add the disciplin to
	 * @param beforeDisciplin The disciplin of the athlet to add the new one before
	 * @param toAdd The disciplin to add
	 */
	public int addElement(Athlet toAddIn, Disciplin beforeDisciplin, Disciplin toAdd) {
		logger.info("Model: Disciplin before other one in athlet");
		int allAthletsSize = mAthlets.size();
		int returnValue = -1;
		for (int i = 0; i < allAthletsSize; i++)
		{
			Athlet athFound = mAthlets.get(i);
			if (athFound.equals(toAddIn) == true) {

				Vector<Disciplin> athsDisciplins = athFound.getDisciplins();
				for (int d = 0; d < athsDisciplins.size(); d++)
				{
					Disciplin disFound = athsDisciplins.get(d);
					if (disFound.equals(beforeDisciplin) == true) {
						
						// add to this athlet
						athsDisciplins.add(d, toAdd);
						athFound.setDisciplins(athsDisciplins);
						mAthlets.set( i, athFound);
						
						returnValue = i;		// save the index of the athlet
						break;
					}
				}
			}		
		}
		return returnValue;
	}
	
	
	/** Remove an athlet from the competition
	 * 
	 * @param toRemove The athlet to remove
	 */
	protected int removeElement(Athlet toRemove) {
		int returnValue = -1;
		int allAthletsSize = mAthlets.size();
		for (int i = 0; i < allAthletsSize; i++)
		{
			Athlet athFound = mAthlets.get(i);
			if (athFound.equals(toRemove) == true) {
				// remove this athlet
				mAthlets.remove(i);
				returnValue = i;		// save the index
				break;
			}			
		}
		return returnValue;	
	}


	/** Remove a disciplin from the competition
	 * 
	 * @param athletIndex The index of for the athlet where the disciplin should be removed
	 * @param toRemove The disciplin to remove
	 */
	protected int removeElement(int athletIndex, Disciplin toRemove) {
		Athlet athletToRemoveFrom = mAthlets.get(athletIndex);

		int returnValue = -1;
		Vector<Disciplin> existingDiscVector = athletToRemoveFrom.getDisciplins();
		int allDisciplinsSize = existingDiscVector.size();
		for (int i = 0; i < allDisciplinsSize; i++)
		{
			Disciplin disciplinFound = existingDiscVector.get(i);
			if (disciplinFound.equals(toRemove) == true) {
				// remove this disciplin
				existingDiscVector.remove(i);
				athletToRemoveFrom.setDisciplins(existingDiscVector);				
				returnValue = i;		// save the index
				break;
			}			
		}
		return returnValue;			
	}
		
	/** Remove a disciplin from an athlet of the competition
	 * 
	 * @param toRemoveIn The athlet to remove the disciplin from
	 * @param toRemove The disciplin to remove
	 */
	protected int removeElement(Athlet toRemoveIn, Disciplin toRemove) {
		int allAthletsSize = mAthlets.size();
		int returnValue = -1;
		for (int i = 0; i < allAthletsSize; i++)
		{
			Athlet athFound = mAthlets.get(i);
			if (athFound.equals(toRemoveIn) == true) {
				// remove from this athlet
				int removeDisciplinIndex = removeElement(i, toRemove);
				returnValue = removeDisciplinIndex;		// save the index
				break;
			}			
		}
		return returnValue;
	}
	

	@SuppressWarnings("unchecked")
	@Override
	public Object clone() {
		Competition newCompetition = new Competition();
		newCompetition.mDescription = mDescription; 
		newCompetition.mPlace = mPlace;
		newCompetition.mDate = mDate;
		newCompetition.mMaxRegistrationDate = mMaxRegistrationDate;
		newCompetition.mAthlets = (Vector<Athlet>) mAthlets.clone();
		newCompetition.mClubFrom = (Club)mClubFrom.clone();
		newCompetition.mClubTo = (Club)mClubTo.clone();
		return newCompetition;
	}	
}
